/*
 * 2011 Runedev development team
 * http://lazygamerz.org
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser Gereral Public Licence as published by the Free
 * Software Foundation; either version 3 of the Licence, or (at your opinion) any
 * later version.
 *
 * This library is distributed in the hope that it will be usefull, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of merchantability or fitness for a
 * particular purpose. See the GNU Lesser General Public Licence for more details.
 *
 * You should have received a copy of the GNU Lesser General Public Licence along
 * with this library; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, Ma 02111-1307 USA.
 *
 * http://www.gnu.org/licenses/lgpl.html (English)
 * http://gugs.sindominio.net/gnu-gpl/lgpl-es.html (Espa�ol)
 *
 */
package org.lazygamerz.scripting.api;

import java.applet.Applet;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Vector;

import org.rsbot.bot.Bot;
import org.rsbot.client.input.Canvas;
import org.rsbot.script.MouseHandler;

/**
 * Use of the mouse on your computer for the game.
 * @author Runedev Development Team - version 1.0
 */
public class Mouse {

    private int mouseSpeed = DEFAULT_MOUSE_SPEED;
    private final java.util.Random random = new java.util.Random();
    
    private boolean present = false;
    private boolean pressed = false;
    private byte dragLength = 0;

    public Mouse() {
    }

	/**
	 * The maximum distance (in pixels) to move the mouse after clicks in either
	 * direction on both axes.
	 */
	public static final int DEFAULT_MAX_MOVE_AFTER = 0;
    
	/**
	 * The default mouse speed. This is the delay in ms between actual mouse
	 * moves. Lower is faster.
	 */
	public static final int DEFAULT_MOUSE_SPEED = 10;

    /**
     * Obtains the x-coordinate of the current mouse location on the
     * screen.
     * 
     * @return x-coordinate of screen location of the mouse pointer.
     */
    public int getX() {
        return Bot.getClient().getMouse().getX();
    }

    /**
     * Obtains the y-coordinate of the current mouse location on the
     * screen.
     * 
     * @return y-coordinate of screen location of the mouse pointer.
     */
    public int getY() {
        return Bot.getClient().getMouse().getY();
    }
    
    /**
     * Drag the mouse from the current position to a certain other position.
     *
     * @param x The x coordinate to drag to.
     * @param y The y coordinate to drag to.
     */
    public void drag(final int x, final int y) {
        pressMouse(getX(), getY(), true);
        sleepNoException(random(300, 500));
        move(getX(), getY(), x, y);
        sleepNoException(random(300, 500));
        releaseMouse(x, y, true);
    }

    /**
     * Drag the mouse from the current position to a certain other position.
     *
     * @param p The point to drag to.
     * @see #drag(int, int)
     */
    public void drag(final Point p) {
        drag(p.x, p.y);
    }

    /**
     * Clicks the mouse at its current location.
     *
     * @param leftClick <tt>true</tt> to left-click, <tt>false</tt>to right-click.
     */
    public void click(final boolean left) {
        if (!present)  {
            return; /* Can't click off the canvas*/
        }
        
        pressMouse(getX(), getY(), left);
        sleepNoException(random(50, 100));
        releaseMouse(getX(), getY(), left);
    }

    public synchronized void click(final boolean leftClick,
            final int moveAfter) {
        
    	click(leftClick);
        
        if (moveAfter > 0) {
            Bot.game.sleep(random(50, 350));
            final Point pos = getLocation();
            move(pos.x - moveAfter, pos.y - moveAfter, moveAfter * 2, moveAfter * 2);
        }
    }

    /**
     * Moves the mouse to a given location then clicks.
     *
     * @param x         x coordinate
     * @param y         y coordinate
     * @param leftClick <tt>true</tt> to left-click, <tt>false</tt>to right-click.
     */
    public void click(final int x, final int y, final boolean leftClick) {
        click(x, y, 0, 0, leftClick);
    }

    /**
     * Moves the mouse to a given location with given randomness then clicks.
     *
     * @param x         x coordinate
     * @param y         y coordinate
     * @param randX     x randomness (added to x)
     * @param randY     y randomness (added to y)
     * @param leftClick <tt>true</tt> to left-click, <tt>false</tt>to right-click.
     * @see #move(int, int, int, int)
     */
    public synchronized void click(final int x, final int y, final int randX,
            final int randY, final boolean leftClick) {
        move(x, y, randX, randY);
        Bot.game.sleep(random(50, 350));
        click(leftClick, DEFAULT_MAX_MOVE_AFTER);
    }

    /**
     * Moves the mouse to a given location with given randomness then clicks,
     * then moves a random distance up to <code>afterOffset</code>.
     *
     * @param x             x coordinate
     * @param y             y coordinate
     * @param randX         x randomness (added to x)
     * @param randY         y randomness (added to y)
     * @param leftClick     <tt>true</tt> to left-click, <tt>false</tt>to right-click.
     * @param moveAfterDist The maximum distance in pixels to move on both axes shortly
     *                      after moving to the destination.
     */
    public synchronized void click(final int x, final int y, final int randX,
            final int randY, final boolean leftClick, final int moveAfterDist) {
        move(x, y, randX, randY);
        Bot.game.sleep(random(50, 350));
        click(leftClick, moveAfterDist);
    }

    /**
     * Moves the mouse to a given location then clicks.
     *
     * @param p         The point to click.
     * @param leftClick <tt>true</tt> to left-click, <tt>false</tt>to right-click.
     */
    public void click(final Point p, final boolean leftClick) {
        click(p.x, p.y, leftClick);
    }

    public void click(final Point p, final int x, final int y, final boolean leftClick) {
        click(p.x, p.y, x, y, leftClick);
    }

    /**
     * Moves the mouse to a given location with given randomness then clicks,
     * then moves a random distance up to <code>afterOffset</code>.
     *
     * @param p             The destination Point.
     * @param x             x coordinate
     * @param y             y coordinate
     * @param leftClick     <tt>true</tt> to left-click, <tt>false</tt>to right-click.
     * @param moveAfterDist The maximum distance in pixels to move on both axes shortly
     *                      after moving to the destination.
     */
    public void click(final Point p, final int x, final int y, final
            boolean leftClick, final int moveAfterDist) {
        click(p.x, p.y, x, y, leftClick, moveAfterDist);
    }

    /**
     * Moves the mouse slightly depending on where it currently is and clicks.
     */
    public void clickSlightly() {
        final Point p = new Point((int) (getLocation().getX() +
                (Math.random() * 50 > 25 ? 1 : -1) * (30 + Math.random() * 90)),
                (int) (getLocation().getY() + (Math.random() * 50 > 25 ? 1 : -1)
                * (30 + Math.random() * 90)));
        if (p.getX() < 1 || p.getY() < 1 || p.getX() > 761 || p.getY() > 499) {
            clickSlightly();
            return;
        }
        click(p, true);
    }

    /**
     * @return <tt>true</tt> if Sweed_Raver's mouse methods should be used by
     *         default; otherwise <tt>false</tt>.
     */
    public boolean getPath() {
        return !MouseHandler.DEFAULT_MOUSE_PATH;
    }

    /**
     * Generates a randomized point array, used as a mouse path.
     *
     * @param amount    The amount of points for the mouse path to contain,
     *                  including the destination.
     * @param end       The destination.
     * @return          A point array, used as a mouse path or null if failed
     *                  (Most likely because the amount was negative or 0).
     *                  If you enter 1 as amount, the return would be the
     *                  destination.
     */
    public Point[] generatePath(int amount, Point end) {
        try {
            if (amount <= 0) {
                return null;
            }
            Point[] path = new Point[amount];
            Point curPos = getLocation();
            for (int i = 0; i < path.length; i++) {
                path[i] = new Point();
                if (i == path.length - 1) {
                    path[i].setLocation(end);
                } else if (i != 0) {
                    path[i].setLocation(
                            path[i - 1].x > end.x ? random(
                            end.x, path[i - 1].x) : random(
                            path[i - 1].x, end.x),
                            path[i - 1].y > end.y ? random(
                            end.y, path[i - 1].y) : random(
                            path[i - 1].y, end.y));
                } else {
                    path[i].setLocation(
                            curPos.x > end.x ? random(end.x,
                            curPos.x) : random(curPos.x, end.x),
                            curPos.y > end.y ? random(end.y,
                            curPos.y) : random(curPos.y, end.y));
                }
            }
            Vector<Point> unsorted = new Vector<Point>();
            unsorted.addAll(Arrays.asList(path));
            Vector<Point> sorted = new Vector<Point>();
            for (Point element : path) {
                if (element == null) {
                    continue;
                }
                int b = 0;
                int dist = 0;
                for (int a = 0; a < unsorted.size(); a++) {
                    if ((int) Math.hypot(
                            unsorted.get(a).getX() - end.getX(),
                            unsorted.get(a).getY() - end.getY()) >= dist) {
                        dist = (int) Math.hypot(unsorted.get(a).getX()
                                - end.getX(), unsorted.get(a).getY()
                                - end.getY());
                        b = a;
                    }
                }
                sorted.add(unsorted.get(b));
                unsorted.remove(b);
            }
            Point[] Path = new Point[sorted.size()];
            for (int i = 0; i < sorted.size(); i++) {
                Path[i] = sorted.get(i);
            }
            return Path;
        } catch (Exception e) {
        }
        return null;
    }

    /**
     * @param max       The maximum distance outwards.
     * @return          A random x value between the current client location
     *                  and the max distance outwards.
     */
    public int getRandomX(final int max) {
        final Point p = getLocation();
        if (p.x < 0 || max <= 0) {
            return -1;
        }
        if (random(0, 2) == 0) {
            return p.x - random(0, p.x < max ? p.x : max);
        } else {
            final int dist = Bot.game.getWidth() - p.x;
            return p.x + random(1, dist < max && dist > 0 ? dist : max);
        }
    }

    /**
     * @param max       The maximum distance outwards.
     * @return          A random y value between the current client location
     *                  and the max distance outwards.
     */
    public int getRandomY(final int max) {
        final Point p = getLocation();
        if (p.y < 0 || max <= 0) {
            return -1;
        }
        if (random(0, 2) == 0) {
            return p.y - random(0, p.y < max ? p.y : max);
        } else {
            final int dist = Bot.game.getHeight() - p.y;
            return p.y + random(1, dist < max && dist > 0 ? dist : max);
        }
    }

    /**
     * The location of the bot mouse; or Point(-1, -1) if off screen.
     *
     * @return      A <tt>Point</tt> containing the bot mouse x & y coordinates.
     */
    public Point getLocation() {
    	org.rsbot.client.input.Mouse m = Bot.getClient().getMouse();
    	
        return new Point(m.getX(), m.getY());
    }

    /**
     * @return      The <tt>Point</tt> at which the bot mouse was last clicked.
     */
    public Point getPressLocation() {
        final org.rsbot.client.input.Mouse m = Bot.game.client().getMouse();
        return new Point(m.getPressX(), m.getPressY());
    }

    /**
     * @return      The system time when the bot mouse was last pressed.
     */
    public long getPressTime() {
        final org.rsbot.client.input.Mouse mouse = Bot.game.client().getMouse();
        return mouse == null ? 0 : mouse.getPressTime();
    }

    /**
     * Gets the mouse speed.
     *
     * @return      the current mouse speed.
     * @see         #setSpeed(int)
     */
    public int getSpeed() {
        return mouseSpeed;
    }

    /**
     * Hops mouse to the specified coordinate.
     *
     * @param x         The x coordinate.
     * @param y         The y coordinate
     */
    public synchronized void hop(final int x, final int y) {
        move(x, y);
    }

    /**
     * Hops mouse to the specified point.
     *
     * @param p         The coordinate point.
     * @see             #hop(Point)
     */
    public void hop(final Point p) {
        move(p.x, p.y);
    }

    /**
     * Hops mouse to the certain coordinate.
     *
     * @param x             The x coordinate.
     * @param y             The y coordinate.
     * @param randX         The x coordinate randomization.
     * @param randY         The y coordinate randomization.
     * @see                 #hop(int, int)
     */
    public void hop(final int x, final int y, final int randX, final int randY) {
        move(x + random(-randX, randX), y + random(-randX, randY));
    }

    /**
     * Hops mouse to the certain point.
     *
     * @param p         The coordinate point.
     * @param randX     The x coordinate randomization.
     * @param randY     The y coordinate randomization.
     * @see             #hop(int, int, int, int)
     */
    public void hop(final Point p, final int randX, final int randY) {
        hop(p.x, p.y, randX, randY);
    }

    /**
     * @return          <tt>true</tt> if the bot mouse is present.
     */
    public boolean isPresent() {
        final org.rsbot.client.input.Mouse mouse = Bot.game.client().getMouse();
        return mouse != null && mouse.isPresent();
    }

    /**
     * @return          <tt>true</tt> if the bot mouse is pressed.
     */
    public boolean isPressed() {
        final org.rsbot.client.input.Mouse mouse = Bot.game.client().getMouse();
        return mouse != null && mouse.isPressed();
    }

    /**
     * Moves the mouse to the specified point at a certain speed.
     *
     * @param speed         The lower, the faster.
     * @param x             The x destination.
     * @param y             The y destination.
     * @param randX         X-axis randomness (added to x).
     * @param randY         X-axis randomness (added to y).
     * @param afterOffset   The maximum distance in pixels to move on both
     *                      axes shortly after moving to the destination.
     * @param mousePaths    <tt>true</tt> to enable Sweed's mouse splines,
     *                      otherwise <tt>false</tt>.
     * @see                 #getMouseSpeed()
     */
    public void move(int speed, int x, int y, int randX, int randY,
            int afterOffset, boolean mousePaths) {
        if (x != -1 || y != -1) {
            move(speed, x, y, randX, randY, mousePaths);
            
            if (afterOffset > 0) {
                Bot.game.wait(random(60, 300));
                Point pos = getLocation();
                move(pos.x - afterOffset, pos.y - afterOffset,
                        afterOffset * 2, afterOffset * 2);
            }
        }
    }

    /**
     * Moves the mouse to the specified point at a certain sped.
     *
     * @param speed      the lower, the faster.
     * @param x          the x value
     * @param y          the y value
     * @param randomX    x-axis randomness (gets added to x)
     * @param randomY    y-axis randomness (gets added to y)
     * @param MousePaths Whether or not to use Mouse Path generator
     */
    public void move(final int speed, final int x, final int y, final int randomX, final int randomY, final boolean MousePaths) {
    	Canvas canvas = Bot.getCanvas();
    	org.rsbot.client.input.Mouse m = Bot.getClient().getMouse();
    	
        int thisX = m.getX(), thisY = m.getY();
        
        if (!canvas.contains(thisX, thisY)) {
            /*on which side of canvas should it enter*/
            switch (random(1, 5)) {
                case 1:
                    thisX = -1;
                    thisY = random(0, canvas.getHeight());
                    break;
                case 2:
                    thisX = random(0, canvas.getWidth());
                    thisY = canvas.getHeight() + 1;
                    break;
                case 3:
                    thisX = canvas.getWidth() + 1;
                    thisY = random(0, canvas.getHeight());
                    break;
                case 4:
                    thisX = random(0, canvas.getWidth());
                    thisY = -1;
                    break;
            }
        }
        
        if (MousePaths) {
            final Point[] path = generatePath((int) Math.hypot(thisX - x, thisX - y) / 100 + random(1, 3), new Point(x, y));
            if (path == null) {
                new Exception("Mouse paths were enabled, and the path was returned null. Please report on forums: ").printStackTrace();
            }
            
            move(speed, thisX, thisY, path[0].x, path[0].y, 0, 0);
            
            for (int i = 1; i < path.length; i++) {
                try {
                    if (i == path.length - 1) {
                        move(speed, path[i - 1].x, path[i - 1].y, random(path[i].x, path[i].x + randomX), random(path[i].y, path[i].y + randomY),0,0);
                    } else {
                    	move(speed, path[i - 1].x, path[i - 1].y, path[i].x, path[i].y, 0, 0);
                    }

                } catch (final Exception e) {
                    e.printStackTrace();
                }
            }

        } else {
        	move(speed, thisX, thisY, random(x, x + randomX), random(y, y + randomY), 0, 0);
        }
    }
    
    /**
     * Moves mouse to location (x,y) at default speed.
     *
     * @param x x       coordinate
     * @param y y       coordinate
     * @see             #move(int, int, int, int)
     * @see             #setSpeed(int)
     */
    public void move(final int x, final int y) {
    	org.rsbot.client.input.Mouse m = Bot.getClient().getMouse();

        /*Firstly invoke drag events*/
        if (pressed) {
            final MouseEvent me = new MouseEvent(getTarget(), MouseEvent.MOUSE_DRAGGED, System.currentTimeMillis(), 0, x, y, 0, false);

            m.sendEvent(me);

            if ((dragLength & 0xFF) != 0xFF) {
                dragLength++;
            }
        }

        if (!present) {
            if (isOnCanvas(x, y)) { 
                final MouseEvent me = new MouseEvent(getTarget(), MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, x, y, 0, false);
                present = true;
                m.sendEvent(me);
            } else
                return;
        }
        if (!isOnCanvas(x, y)) {
            final MouseEvent me = new MouseEvent(getTarget(), MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), 0, x, y, 0, false);
            present = false;
            m.sendEvent(me);
            return;
        }
        if (!pressed) {
            final MouseEvent me = new MouseEvent(getTarget(), MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, x, y, 0, false);
            m.sendEvent(me);
        }
    }

    public void move(int x, int y, boolean paths) {
        move(getSpeed(), x, y, 0, 0, 0, paths);
    }


    /**
     * @see     #move(int, int, int, int, int, int)
     */
    public void move(final int x, final int y, final int offset) {
        move(getSpeed(), x, y, 0, 0, offset);
    }

    /**
     * Moves the mouse to the specified point at default speed.
     *
     * @param x         The x destination.
     * @param y         The y destination.
     * @param randX     x-axis randomness (added to x).
     * @param randY     y-axis randomness (added to y).
     * @see             #move(int, int, int, int, int, int)
     * @see             #setSpeed(int)
     */
    public void move(final int x, final int y, final int randX, final int randY) {
        move(getSpeed(), x, y, randX, randY, 0);
    }

    /**
     * Moves the mouse to the specified point at a certain speed.
     *
     * @param speed         The lower, the faster.
     * @param x             The x destination.
     * @param y             The y destination.
     * @param randX         x-axis randomness (added to x).
     * @param randY         y-axis randomness (added to y).
     * @see                 #move(int, int, int, int, int, int)
     */
    public void move(final int speed, final int x, final int y,
            final int randX, final int randY) {
        move(speed, x, y, randX, randY, 0);
    }

    /**
     * Moves the mouse to the specified point at a certain speed, then moves a
     * random distance up to <code>afterOffset</code>.
     *
     * @param speed       The lower, the faster.
     * @param x           The x destination.
     * @param y           The y destination.
     * @param randX       X-axis randomness (added to x).
     * @param randY       X-axis randomness (added to y).
     * @param offset The maximum distance in pixels to move on both axes shortly
     *                    after moving to the destination.
     */
    public synchronized void move(final int speed, final int x, final int y, final int randX, final int randY, final int offset) {
        if (x != -1 || y != -1) {
            move(speed, x, y, randX, randY, false);
            
            if (offset > 0) {
                Bot.game.sleep(random(60, 300));
                final Point pos = getLocation();
                move(pos.x - offset, pos.y - offset, offset * 2, offset * 2);
            }
        }
    }

    /**
     * @see     #move(int, int, int, int, int, int)
     */
    public void move(final int speed, final Point p) {
        move(speed, p.x, p.y, 0, 0, 0);
    }

    /**
     * @see     #move(int, int, int, int)
     */
    public void move(final Point p) {
        move(p.x, p.y, 0, 0);
    }

    /**
     * @see     #move(int, int, int, int, int, int)
     */
    public void move(final Point p, final int afterOffset) {
        move(getSpeed(), p.x, p.y, 0, 0, afterOffset);
    }

    /**
     * @see     #move(int, int, int, int)
     */
    public void move(final Point p, final int randX, final int randY) {
        move(p.x, p.y, randX, randY);
    }

    /**
     * @see     #move(int, int, int, int, int, int)
     */
    public void move(final Point p, final int randX, final int randY,
            final int offset) {
        move(getSpeed(), p.x, p.y, randX, randY, offset);
    }

    public void move(int Speed, Point p, boolean paths) {
        move(Speed, p.x, p.y, 0, 0, 0, paths);
    }

    public void move(Point p, boolean paths) {
        move(getSpeed(), p.x, p.y, 0, 0, 0, paths);
    }

    public void move(Point p, int offset, boolean paths) {
        move(getSpeed(), p.x, p.y, 0, 0, offset, paths);
    }

    public void move(Point p, int randX, int randY, boolean paths) {
        move(getSpeed(), p.x, p.y, randX, randY, 0, paths);
    }

    /**
     * Moves mouse through a specified mouse path.
     *
     * @param path      The path to move mouse through.
     * @param randX     The amount each point can be randomized in the X-axis.
     * @param randY     The amount each point can be randomized in the Y-axis.
     */
    public void move(Point[] path, int randX, int randY) {
        for (Point p : path) {
            move(p, randX, randY);
        }
    }
    
	/**
	 * Moves the mouse from a position to another position with randomness
	 * applied.
	 *
	 * @param speed
	 *            the speed to move the mouse. Anything under
	 *            {@link #DEFAULT_MOUSE_SPEED} is faster than normal.
	 * @param x1
	 *            from x
	 * @param y1
	 *            from y
	 * @param x2
	 *            to x
	 * @param y2
	 *            to y
	 * @param randX
	 *            randomness in the x direction
	 * @param randY
	 *            randomness in the y direction
	 */
	public void move(final int speed, final int x1, final int y1, final int x2, final int y2, int randX, int randY) {
		if ((x2 == -1) && (y2 == -1))
			/* MouseHandler.log
			* .warning("Non-fatal error. Please post log on forums. ("
			* + x2 + "," + y2 + ")");
                         *
                         */
			return;
		if (randX <= 0) {
			randX = 1;
		}
		if (randY <= 0) {
			randY = 1;
		}
		try {
			if ((x2 == x1) && (y2 == y1))
				return;
			final Point[] controls = MouseHandler.generateControls(x1, y1, x2 + random.nextInt(randX), y2 + random.nextInt(randY), 50, 120);
			final Point[] spline = MouseHandler.generateSpline(controls);
			final long timeToMove = MouseHandler.fittsLaw(Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), 10);
			final Point[] path = MouseHandler.applyDynamism(spline, (int) timeToMove, MouseHandler.DEFAULT_MOUSE_SPEED);
			for (final Point aPath : path) {
				move(aPath.x, aPath.y);
				try {
					Thread.sleep(Math.max(0, speed - 2 + random.nextInt(4)));
				} catch (final InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		} catch (final Exception e) {
			/*MouseHandler.log.info("Error moving mouse: " + e);
			* MouseHandler.log.info("Source: " + x1 + "," + y1);
			* MouseHandler.log.info("Dest:   " + x2 + "," + y2);
			* MouseHandler.log.info("Randx/Randy: " + randX + "/" + randY);
			* e.printStackTrace();
                         *
                         */
		}
	}
	
    /**
     * Moves the mouse off the screen in a random direction.
     */
    public void moveOffScreen() {
        if (isPresent()) {
            switch (random(0, 4)) {
                case 0: // up
                    move(random(-10, Bot.game.getWidth() + 10),
                            random(-100, -10));
                    break;
                case 1: // down
                    move(random(-10, Bot.game.getWidth() + 10),
                            Bot.game.getHeight() + random(10, 100));
                    break;
                case 2: // left
                    move(random(-100, -10),
                            random(-10, Bot.game.getHeight() + 10));
                    break;
                case 3: // right
                    move(random(10, 100) + Bot.game.getWidth(),
                            random(-10, Bot.game.getHeight() + 10));
                    break;
            }
        }
    }

    /**
     * Moves the mouse a random distance between 1 and  maxDistance from the
     * current position of the mouse by generating a random vector and then
     * multiplying it by a random number between 1 and maxDistance. The maximum
     * distance is cut short if the mouse would go off screen in the direction
     * it chose.
     *
     * @param max       The maximum distance the cursor will move (exclusive)
     * @author Enfilade
     */
    public void moveRandomly(final int max) {
        moveRandomly(1, max);
    }

    /**
     * Moves the mouse a random distance between minDistance and maxDistance
     * from the current position of the mouse by generating random vector and
     * then multiplying it by a random number between  minDistance and maxDistance.
     * The maximum distance is cut short if the  mouse would go off screen in
     * the direction it chose.
     *
     * @param min           The minimum distance the cursor will move
     * @param max           The maximum distance the cursor will move (exclusive)
     * @author Enfilade
     */
    public void moveRandomly(final int min, final int max) {
        /* Generate a random vector for the direction the mouse will move in */
        double xvec = Math.random();
        if (random(0, 2) == 1) {
            xvec = -xvec;
        }
        double yvec = Math.sqrt(1 - xvec * xvec);
        if (random(0, 2) == 1) {
            yvec = -yvec;
        }
        /* Start the maximum distance at maxDistance */
        double distance = max;
        /* Get the current location of the cursor */
        final Point p = getLocation();
        /* Calculate the x coordinate if the mouse moved the maximum distance */
        final int maxX = (int) Math.round(xvec * distance + p.x);
        /*
         * If the maximum x is offscreen, subtract that distance/xvec from the
         * maximum distance so the maximum distance will give a valid X
         * coordinate
         */
        distance -= Math.abs((maxX - Math.max(0, Math.min(Bot.game.getWidth(), maxX))) / xvec);
        /* Do the same thing with the Y coordinate */
        final int maxY = (int) Math.round(yvec * distance + p.y);
        distance -= Math.abs((maxY - Math.max(0, Math.min(Bot.game.getHeight(), maxY))) / yvec);
        /*
         * If the maximum distance in the generated direction is too small,
         * don't move the mouse at all
         */
        if (distance < min) {
            return;
        }
        /*
         * With the calculated maximum distance, pick a random distance to move
         * the mouse between maxDistance and the calculated maximum distance
         */
        distance = random(min, (int) distance);
        /* Generate the point to move the mouse to and move it there */
        move((int) (xvec * distance) + p.x, (int) (yvec * distance) + p.y);
    }

    /**
     * Moves the mouse slightly depending on where it currently is.
     */
    public void moveSlightly() {
        final Point p = new Point(
                (int) (getLocation().getX() + (Math.random() * 50 > 25 ? 1 : -1) * (30 + Math.random() * 90)),
                (int) (getLocation().getY() + (Math.random() * 50 > 25 ? 1 : -1) * (30 + Math.random() * 90)));
        if (p.getX() < 1 || p.getY() < 1 || p.getX() > 761 || p.getY() > 499) {
            moveSlightly();
            return;
        }
        move(p);
    }

    /**
     * Changes the mouse speed
     *
     * @param speed         The speed to move the mouse at. 4-10 is advised,
     *                      1 being the fastest.
     * @see                 #getSpeed()
     */
    public void setSpeed(final int speed) {
        mouseSpeed = speed;
    }

    /**
     * Returns a random double in the specified range with a Gaussian
     * distribution having a mean of the absolute value of the difference
     * between min and max and a standard deviation of 1.  The returned
     * value will honor the min and max boundaries.
     *
     * @param min   Minimum value (inclusive).
     * @param max   Maximum value (inclusive).
     * @return      The random <code>double</code> generated.
     */
    public double random(double min, double max) {
    	double mean = min + Math.abs(max-min)/2;

    	double next;
    	
    	// The loop ensures we honor the min/max values.
    	do  {
    		next = mean + random.nextGaussian();
    	} while (next<min || next>max);    	 
    	
        return next;
    }

    /**
     * Returns a random int in the specified range with a Gaussian
     * distribution having a mean of the absolute value of the difference
     * between min and max and a standard deviation of 1.  The returned
     * value will honor the min and max boundaries.
     *
     * @param min   Minimum value (inclusive).
     * @param max   Maximum value (inclusive).
     * @return      The random <code>int</code> generated.
     */
    public int random(int min, int max) {
    	int mean = min + Math.abs(max-min)/2;
    	int next;
    	
    	// The loop ensures we honor the min/max values.
    	do  {
    		next = (int) (mean + random.nextGaussian());
    	} while (next<min || next>max);    	 
    	
        return next;
    }

    /**
     * Returns a random int in the specified range with a Gaussian
     * distribution having a mean of the absolute value of the difference
     * between min and max and a standard deviation of sd.  The returned
     * value will honor the min and max boundaries.
     *
     * @param min   Minimum value (inclusive).
     * @param max   Maximum value (inclusive).
     * @return      The random <code>int</code> generated.
     */
    public int random(int min, int max, int sd) {
        int mean = min + Math.abs(max - min) / 2;
        int next;
        do {
        	next = (int) (mean + random.nextGaussian() * sd);
        } while (next < min || next > max);
        return next;
    }
    
    private Applet getTarget() {
        return (Applet) Bot.getClient();
    }
    
    private void sleepNoException(final long t) {
        try {
            Thread.sleep(t);
        } catch (final Exception e) {
        }
    }
    
    private void pressMouse(final int x, final int y, final boolean left) {
        if (pressed || !present)  {
            return;
        }
        
        final MouseEvent me = new MouseEvent(getTarget(), MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 0, x, y, 1, false, left ? MouseEvent.BUTTON1 : MouseEvent.BUTTON3);
        pressed = true;
        Bot.getClient().getMouse().sendEvent(me);
    }
    
    private void releaseMouse(final int x, final int y, final boolean leftClick) {
        if (!pressed)
            return;
        MouseEvent me = new MouseEvent(getTarget(), MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), 0, x, y, 1, false, leftClick ? MouseEvent.BUTTON1 : MouseEvent.BUTTON3);
        pressed = false;
        Bot.getClient().getMouse().sendEvent(me);

        if ((dragLength & 0xFF) <= 3) {
            me = new MouseEvent(getTarget(), MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), 0, x, y, 1, false, leftClick ? MouseEvent.BUTTON1 : MouseEvent.BUTTON3);
            Bot.getClient().getMouse().sendEvent(me);
        }
        
        /* reset*/
        dragLength = 0;
    }
    
	/**
	 * Moves the mouse from a position to another position with randomness
	 * applied.
	 *
	 * @param speed
	 *            the speed to move the mouse. Anything under
	 *            {@link #DEFAULT_MOUSE_SPEED} is faster than normal.
	 * @param x1
	 *            from x
	 * @param y1
	 *            from y
	 * @param x2
	 *            to x
	 * @param y2
	 *            to y
	 * @param randX
	 *            randomness in the x direction
	 * @param randY
	 *            randomness in the y direction
	 */
	public void moveMouse(final int speed, final int x1, final int y1, final int x2, final int y2, int randX, int randY) {
		if ((x2 == -1) && (y2 == -1))
			/* MouseHandler.log
			* .warning("Non-fatal error. Please post log on forums. ("
			* + x2 + "," + y2 + ")");
                         *
                         */
			return;
		if (randX <= 0) {
			randX = 1;
		}
		if (randY <= 0) {
			randY = 1;
		}
		try {
			if ((x2 == x1) && (y2 == y1))
				return;
			final Point[] controls = MouseHandler.generateControls(x1, y1, x2 + random.nextInt(randX), y2 + random.nextInt(randY), 50, 120);
			final Point[] spline = MouseHandler.generateSpline(controls);
			final long timeToMove = MouseHandler.fittsLaw(Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), 10);
			final Point[] path = MouseHandler.applyDynamism(spline, (int) timeToMove, DEFAULT_MOUSE_SPEED);
			for (final Point aPath : path) {
				hop(aPath.x, aPath.y);
				
				try {
					Thread.sleep(Math.max(0, speed - 2 + random.nextInt(4)));
				} catch (final InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		} catch (final Exception e) {
			/*MouseHandler.log.info("Error moving mouse: " + e);
			* MouseHandler.log.info("Source: " + x1 + "," + y1);
			* MouseHandler.log.info("Dest:   " + x2 + "," + y2);
			* MouseHandler.log.info("Randx/Randy: " + randX + "/" + randY);
			* e.printStackTrace();
                         *
                         */
		}
	}
	
    private static boolean isOnCanvas(final int x, final int y) {
    	return Bot.getCanvas().contains(x,y);
    }
}
